iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0
Modern Web

一些讓你看來很強的全端- trcp 伴讀系列 第 6

Day-06. 一些讓你看來很強的全端 TRPC 伴讀 - Define Routers

  • 分享至 

  • xImage
  •  

接下來的幾天會開始介紹 trpc backendclient端的使用:

backend 部分:

  1. Define Routers
  2. Define Procedures
  3. Input & Output Validators
  4. Merging Routers
  5. Context
  6. Middlewares
  7. host Adapters
  8. Server Side Calls
  9. Authorization
  10. Error Handling
  11. Metadata
  12. Response Caching

client 部分:

  1. Custom header
  2. React Query Integration
  3. Next.js Integration
  4. links

額外部分:

  1. tRPC Collection
  2. Extra Information

本次 trpc 章節會以上面的 list 為主,筆者會盡量補齊 trpc 的全家桶給大家學習,如果讀者有什麼想了解的部分可以下方討論喔~本次的 trpc 會全部以 Nextjs 這個 framework 為主,trpc 他有提供大部分 nodejs 相關生態系給大家使用:

backend:

  • Express
  • Fastify
  • Nextjs
  • Remix
  • SolidStart
  • AWS Lambda function

client:

  • Nextjs
  • Remix
  • React

大家可以根據喜愛的框架去做選擇,會這次會用 Nextjs 原因是 trpc 的主要生態系會是以 Nextjs 為主,所以為了帶給讀者更完整齊全的教學,再加上本人比較習慣 Nextjs 所以會選用他,然後文章大部分的範例都是從官網來的,筆者會以官網的 demo 為主慢慢做延伸。

正文介紹

開始前先起一個空白 next 專案

npx create-next-app@latest next_demo  

Define Routers

step1 install package

$ npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod

step2 Add folder

首先在先 src 中新增 api/root.tsnext page

src
├── pages
│   └── _app.tsx
└──  server
    └── api
        └── trpc.ts
// _app.tsx
import { api } from '@/utils/api'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

export default MyApp;

所有 route 需需要透過 initTRPC.create() 去初始化,所有的 api handler 都是透過 t 這個 instance 創建出來的。

// ~/server/api/trpc.ts
import { initTRPC } from '@trpc/server';

// You can use any variable name you like.
// We use t to keep things simple.
const t = initTRPC.create();

export const router = t.router;
export const middleware = t.middleware;
export const publicProcedure = t.procedure;

step3 definded route

新增 root.ts 用於管理所有的 route

src
├── pages
│   ├── _app.tsx
│   ├── api
│   │   └── trpc
│   │       └── [trpc].ts
│   └── index.tsx
├── server
    └── api
        ├── root.ts
        └── trpc.ts

以下的範例就是創建一個 greeting apiquery 就是你 api 的結果,這邊要注意個是記得加 AppRouter 這個 type 因為之後 client端會去使用到喔

// ~/server/api/root.ts
import * as trpc from '@trpc/server';
import { publicProcedure, router } from './trpc';
 
const appRouter = router({
  greeting: publicProcedure.query(() => 'hello tRPC v10!'),
});
 
// Export only the type of a router!
// This prevents us from importing server code on the client.
export type AppRouter = typeof appRouter;

step4 definded api endpoint

新增 ~/app/api/trpc/[trpc]/route.ts file

src
├── pages
│   ├── _app.tsx
│   ├── api
│   │   └── trpc
│   │       └── [trpc].ts
│   └── index.tsx
├── server
    └── api
        ├── root.ts
        └── trpc.ts

因為 nextjsapp folderpage folder 使用差異,但這邊筆者建議使用 page folder 為主, trpc 目前對於 server compnent 的完整度還不齊全,但還是簡單 demo 一下 寫法差異,這邊要注意一下不能兩者混用,這樣 next 會不知道他 api 要吃 page 的還是 app 的。

// ~/pages/api/trpc/[trpc].ts

import { createNextApiHandler } from '@trpc/server/adapters/next';

import { appRouter } from '@/server/api/root';

export default createNextApiHandler({
  router: appRouter,
  createContext: () => ({}),
});
// ~/app/api/trpc/[trpc]/route.ts
import { appRouter } from '@/server/api/root';
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext: () => ({})
  });
export { handler as GET, handler as POST };

step5 check result

最後查看結果,恭喜你!!你成功做到人生第一隻用 trpc 做的 api 了!!有沒有找到人生第一次寫 hello world 的感動呢XD

step6 create client api instance

最後需要添加 api instanceclient 端去接,接著新增 utils/api.ts 檔案

src
├── pages
│   ├── _app.tsx
│   ├── api
│   │   └── trpc
│   │       └── [trpc].ts
│   └── index.tsx
├── server
│   └── api
│       ├── root.ts
│       └── trpc.ts
└── utils
    └── api.ts

下面一一介紹 createTRPCNext 方法:

// utils/api.ts


import { AppRouter } from '@/server/api/root';
import { httpBatchLink, loggerLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';


export const api = createTRPCNext<AppRouter>({
  config(opts) {
    return {
      links: [
        httpBatchLink({
          /**
           * If you want to use SSR, you need to use the server's full URL
           * @link https://trpc.io/docs/ssr
           **/
          url: `http://localhost:3000`,

          // You can pass any HTTP headers you wish here
          async headers() {
            return {
            };
          },
        }),
      ],
    };
  },
  /**
   * @link https://trpc.io/docs/ssr
   **/
  ssr: false,
});

httpBatchLink : trpc 是透過 https request 去請求連結到 trpcprocedure ,然後處去發 api route 邏輯,如上面定義的 route
headers : 這邊跟就是放你 req header 的地方,例如常見的 Authorization 你可以這樣寫。

httpBatchLink({
  url: `http://localhost:3000`,
  async headers() {
    return {
      Authorization: `Bearer ${token}`
    };
  }
}),

ssr: 因為 trpc 預設會在 servergetInitialPropsprefetch,這樣會造成 api response 時間太長,解法有兩個在 headercache-control,或是把 ssr: false,然後透過 ssr helper 手動添加特定頁面做 prefetchssr helper 日後會跟大家介紹,這邊記得先加 ssr:false

詳細資料可以看這邊

step7 connect to client

最後記得在 _app.tsxapi.withTRPC 這樣 trpc 才能在 client 端呼叫喔~

//_app.tsx

import { api } from '@/utils/api'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

export default api.withTRPC(MyApp);

添加 index.tsx

src
├── pages
│   ├── _app.tsx
│   ├── api
│   │   └── trpc
│   │       └── [trpc].ts
│   └── index.tsx
├── server
│   └── api
│       ├── root.ts
│       └── trpc.ts
└── utils
    └── api.ts

讀者看到這邊先恭喜你已經完成 client 端連結拉,那因為 client 端是封裝 react query ,所以使用起來跟 react query 一樣呦~

// 
import { api } from "@/utils/api";
import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import Head from "next/head";
import Link from "next/link";

export default function Home() {
  const { data, isLoading, isError } = api.greeting.useQuery()

  if (isLoading) return 'isLoading'
  if (isError) return 'isError'
  return (
    <>
      {data}
    </>
  );
}

step8 cache control

trpc 可以透過 responseMeta 來設定 cache-control 的值,這邊簡單介紹下面 demo 用到的內容。

max-age=60 * 60 * 24

代表 1 天內的 response 都是返回 cache 資料,一天後重新發 request

max-age=1, stale-while-revalidate=60 * 60 * 24

代表 1s 內的 response 都是返回 cache 資料 , 1s 到 1 天內在背景更改 cache data,而不是透過 request update cache ,一天後才會重新發 request ,這是一個性能與即時性的平衡用法。

export default createNextApiHandler({
  router: appRouter,
  createContext: createTRPCContext,
  responseMeta(opts) {
    const { ctx, paths, errors, type } = opts
    const allPublic = paths && paths.every(path => path.includes('public'))
    const allOk = errors.length === 0
    const isQuery = type === 'query'
    if (
      allPublic &&
      allOk &&
      isQuery
    ) {
      const ONE_DAY_IN_SECONDS = 60 * 60 * 24
      // max-age=60 * 60 * 24 代表 1 天內 內的 response 都是返回 cache 資料,一天後重新發 request
      // max-age=1, stale-while-revalidate=60 * 60 * 24 代表 1s 內的 response 都是返回 cache 資料 , 1s  - 1 天內 背景更改 cache  data,而不是透過 request update cache ,一天後才會重新發 request ,這是一個性能與即時性的平衡用法。
      return {
        headers: {
          'cache-control': `max-age=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,
        }
      }
    }
    return {}
  }
});

相關連結

github : https://github.com/Danny101201/next_demo/tree/main

✅ 前端社群 :
https://lihi3.cc/kBe0Y


上一篇
Day-05. 一些讓你看來很強的全端 TRPC 伴讀 -TRPC CONTEXT
下一篇
Day-07. 一些讓你看來很強的全端 TRPC 伴讀 - Input / Output Validate
系列文
一些讓你看來很強的全端- trcp 伴讀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Vita Ora
iT邦新手 4 級 ‧ 2023-09-20 17:00:02

請收下我的膝蓋。

是你不嫌棄XD

我要留言

立即登入留言